""" Module to overwrite and extend the basic QNetworkAccessmanager """
import os, mimetypes, re
from PyQt4.QtCore import QUrl, QFile, QIODevice, QVariant, QTimer, SIGNAL, \
        QTemporaryFile, QByteArray
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest


class NetworkAccessManager(QNetworkAccessManager):
    """ NetworkAccessmanger extends the basic QNetworkAccesmanger
        so the returned Data of a Request can be modified or replaced
    """

    def __init__(self, parent=None):
        super(NetworkAccessManager, self).__init__(parent)
        self.replacePattern = {}
        self.__requests     = []
        self.__replys       = []
        self.saveRequests   = False
        self.saveReplys     = False


    def createRequest(
            self, 
            operation, 
            request, 
            outgoingData=None, 
            forceStandard=False, 
            followRedirect=False, 
            cb=None
        ):
        """ :return: QNetworkReply or a subclassed Object thereof
            :param QNetworkAccessManager.Operation operation:
            :param QNetworkRequest request:
            :param outgoingData:
            :type outgoingData: QIODevice or None
            :param bool forceStandard:
            :param bool followRedirect:
            :param cb:
            :type cb: callable (function/method) or None
        """
        scheme = request.url().scheme()

        if self.saveRequests:
            self.__requests.append(request())

        reply = None
        if scheme == "http" or scheme == "https":
            if not forceStandard and ( operation == self.GetOperation or operation == self.PostOperation):
                if not cb:
                    if followRedirect:
                        reply = NetworkReplyRedirect( self, operation, request, outgoingData, self)
                    elif len(self.replacePattern):
                        url = request.url().toString()
                        for pattern, cb in self.replacePattern.items():
                            if pattern.search(url):
                                reply = NetworkReplyMod(self, operation, request, outgoingData, self, cb)
                                break
                else:
                    reply = NetworkReplyMod(self, operation, request, outgoingData, self, cb)

        if not reply:
            reply = super(NetworkAccessManager, self).createRequest(operation, request, outgoingData)

        if self.saveReplys:
            self.__replys.append(reply)

        return reply


    def getReplys(self):
        """ :return: list of replys """
        return self.__replys

    def getRequests(self):
        """ :return: list of requests """
        return self.__requests


    def get(self, request, followRedirect=False, cb=None):
        """ :return: QNetworkReply
            :param QNetworkRequest request:
            :param bool followRedirect:
            :param cb:
            :type cb: callable (function/method) or None

            send a 'get' request
        """

        if not cb and not followRedirect:
            return self.createRequest( self.GetOperation, request, forceStandard=True)
        else:
            return self.createRequest( self.GetOperation, request, followRedirect=followRedirect, cb=cb)


    def setReplacePattern(self, pattern, modBy):
        """
            :param string pattern: regex pattern to match a requested file
            :param modBy:
            :type modBy: callable or pfile path string

            set a replacement pattern
        """
        pattern = re.compile(pattern)

        if callable(modBy):
            cb = modBy
        else:
            def cb(data):
                """ wrap file access in a callable """
                f = QFile(modBy)
                f.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
                data = f.readAll()
                f.close()
                return data

        self.replacePattern[pattern] = cb
    
    def download(self, request, filename):
        """ :param QNetworRequest request:
            :param string filename: path to file

            save requested resource to filename
        """
        reply = self.get(request, True)
        def cb():
            """ wrap file access in a callable """
            with open(filename, "w+") as f:
                f.write(reply.readAll())
        reply.finished.connect(cb)

        

class NetworkReplyReplace(QNetworkReply):
    """ NetworkReply which replaces the content of the given url
        by the content of the given file
    """
    
    def __init__(self, parent, filename, url):
        super(NetworkReplyReplace, self).__init__(parent)

        self.file = os.path.abspath(filename)
        self.setUrl(QUrl(url))

        self.iodev = QFile(self.file)
        self.iodev.open(QIODevice.ReadOnly | self.Unbuffered)

        self.setHeader(QNetworkRequest.ContentTypeHeader, 
                QVariant( mimetypes.guess_type(self.file)[0]))
        self.setHeader(QNetworkRequest.ContentLengthHeader, QVariant(self.iodev.size()))

        self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, QVariant(200))

        QTimer.singleShot(0, self, SIGNAL("readyRead()"))
        QTimer.singleShot(0, self, SIGNAL("finished()"))

        self.open(self.ReadOnly | self.Unbuffered)


    def abort(self):
        """ does nothing """
        pass

    def bytesAvailable(self):
        """ :return: qint64 
            
            data size in bytes
        """
        return self.iodev.size()

    def isSequential(self):
        """ :return: True """
        return True

    def readData(self, maxSize):
        """ :return: qint64
            :param int maxSize:

            read upto *maxSize* data and returns read number of bytes
        """
        return self.iodev.readData(maxSize)

    def readAll(self):
        """ :return: QByteArray

            reads all file data and returns it as QByteArray
        """
        return self.iodev.readAll()






class NetworkReplyRedirect(QNetworkReply):
    """ NetworkReply which follows redirects """

    def __init__(self, parent, operation, request, data, nam ):
        super(NetworkReplyRedirect, self).__init__(parent)
        self.reply0 = nam.createRequest(operation, QNetworkRequest(request), data, True)
        self.reply0.finished.connect(self.__onFinished)
        self.reply0.error.connect(self.error.emit)
        self.setRequest(request)
        self.setUrl(request.url())
        self.nam = nam
        self.request = request
        self.data = data
        self.operation = operation
        self.open(self.ReadOnly)


    def __onFinished(self):
        redirect = self.reply0.attribute( QNetworkRequest.RedirectionTargetAttribute )
        if redirect.isValid and not redirect.isNull():
            request = QNetworkRequest(self.request)
            request.setUrl(QUrl(redirect.toString()))
            self.reply = self.nam.createRequest(self.operation, request, self.data, followRedirect=True)
            self.reply.finished.connect(self.finished.emit)
            self.reply.error.connect(self.error.emit)

        else:
            self.reply = self.reply0
            self.finished.emit()
            
    def abort(self):
        """ does nothing """
        pass

    def bytesAvailable(self):
        """ :return: int

            returns number of bytes in reply data
        """
        if self.reply:
            return self.reply.bytesAvailable()
        else:
            return 0

    def readData(self, maxSize):
        """ :return: qint64
            :param int maxSize:

            read upto *maxSize* data and returns read number of bytes
        """
        return self.reply.readData(maxSize)


    def readAll(self):
        """ :return: QByteArray

            reads all reply data and returns it as QByteArray
        """
        if self.reply:
            return self.reply.readAll()
        else:
            return QByteArray()
        






class NetworkReplyMod(QNetworkReply):
    
    def __init__(self, parent, operation, request, data=None, nam=None, cb=None):
        super(NetworkReplyMod, self).__init__(parent)

        self.reply = nam.createRequest(operation, QNetworkRequest(request), data, followRedirect=True)
        self.reply.finished.connect(self.__onFinished)
        self.reply.error.connect(self.error.emit)
        self.setRequest(request)
        self.setUrl(request.url())
        self.cb = cb
        self.iodev = None
        self.open(self.ReadOnly)


    def __onFinished(self):
        if self.reply.error() == self.NoError:
            self.data = self.cb(self.reply.readAll())
            self.iodev = QTemporaryFile()
            self.iodev.open(QIODevice.ReadWrite | QIODevice.Unbuffered)
            self.iodev.write(self.data)
            self.iodev.seek(0)

        for h in self.reply.rawHeaderList():
            self.setRawHeader(h, self.reply.rawHeader(h))

        statusCode = QNetworkRequest.HttpStatusCodeAttribute
        self.setAttribute(statusCode, self.reply.attribute(statusCode))
        self.setHeader(QNetworkRequest.ContentLengthHeader, QVariant(self.iodev.size()))
        self.setFinished(True)
        QTimer.singleShot(0, self, SIGNAL("readyRead()"))
        QTimer.singleShot(0, self, SIGNAL("finished()"))


    def abort(self):
        """ does nothing """
        pass

    def bytesAvailable(self):
        """ :return: qint64 
            
            data size in bytes
        """
        if self.iodev:
            size = self.iodev.size()
        else:
            size = 0

        return size

    def isSequential(self):
        """ :return: True """
        return True

    def readData(self, maxSize):
        """ :return: qint64
            :param int maxSize:

            read upto *maxSize* data and returns read number of bytes
        """
        return self.iodev.readData(maxSize)


    #def readBufferSize(self):
        #print self.iodev.size()
        #print super(NetworkreplyMod, self).readBufferSize()
        #print self.reply.readBufferSize()

    def readAll(self):
        """ :return: QByteArray

            reads all reply data and returns it as QByteArray
        """
        if self.iodev:
            return self.iodev.readAll()
        else:
            return QByteArray()

